
#include "DataKitIO.h"
#include "datakit.h"
#include "common/logger.h"
#include <future>
#include <memory>
#include <string>
#include <locale>

#include "./Writer/ParasolidWriter.hpp"
#include "./Writer/SolidworksWriter.hpp"
#include "./Writer/ObjWriter.hpp"
#include "./Writer/FBXWriter.hpp"
#include "./Writer/GltfWriter.hpp"
#include "./Writer/PDFWriter.hpp"
#include "./Writer/ParasolidWriteProcess.hpp"
#include "./Writer/SolidworksWriteProcess.hpp"
#include "./Writer/MeshWriteProcess.hpp"
#include "./Writer/PDFWriteProcess.hpp"
#include "./utils/FileUtil.h"
#include "./utils/StringUtil.h"

#ifdef Linux
#include <unistd.h>
#endif

namespace  AMCAX::Exchange::DataKit
{
    class IOImpl
    {
public:
        void EnableReaders();
        int ProcessCADFile(const std::string& inInputFile, const std::string& inOutputFolder);        
    };

    int IOImpl::ProcessCADFile(const std::string& strInputFile, const std::string &strOutputFile)
    {
        Dtk_string inInputFile = strInputFile.c_str();
        Dtk_string outputFile = strOutputFile.c_str();

        DtkErrorStatus stError = dtkNoError;
        Dtk_ErrorStatus stErrorPdf = dtkNoError;
        Dtk_ErrorStatus stErrorXml = dtkNoError;
    
        Dtk_MainDocPtr tmpDoc;
    
        // You Get the current API 
        Dtk_API * dtkAPI = Dtk_API::GetAPI();
    
        LOG_INFO("DataKit Processing file {}", inInputFile.c_str());
    
        Dtk_string fileVersion;
        stError = dtkAPI->GetFileVersion(inInputFile, fileVersion);
    
        if( fileVersion.is_not_NULL() )
        {
            LOG_INFO("File Version = {}", fileVersion.c_str());
        }
        if (stError != dtkNoError)
        {
            LOG_ERROR("Error with file version: {}", dtkTypeError( stError ).c_str());
            return stError;
        }
    
        //You Open the file you want to read and get corresponding Document 
        Dtk_ErrorStatus err = dtkAPI->OpenDocument( inInputFile, tmpDoc );
    
        //If no Error we write the Document
        if( err == dtkNoError && tmpDoc.IsNotNULL() )
        {
            // Check the file extension to determine the writer type
            std::string outputFileExtension = StringUtil::ToLowerCase(FileUtil::GetExtension(strOutputFile));
            // For now we only support Parasolid and Solidworks
            std::unique_ptr<WriterBase> writer;
            std::unique_ptr<WriteProcessBase> writeProcess;
            if (outputFileExtension == ".x_t" || outputFileExtension == ".x_b")
            {
                // Parasolid writer
                writer = std::make_unique<ParasolidWriter>();
                writeProcess = std::make_unique<ParasolidWriteProcess>(inInputFile, std::move(writer));
            }
            else if (outputFileExtension == ".sldprt" || outputFileExtension == ".sldasm")
            {
                // Solidworks writer
                writer = std::make_unique<SolidworksWriter>();
                writeProcess = std::make_unique<SolidworksWriteProcess>(inInputFile, std::move(writer));
            }
            else if (outputFileExtension == ".obj")
            {
                writer = std::make_unique<ObjWriter>();
                writeProcess = std::make_unique<MeshWriteProcess>(inInputFile, std::move(writer));
            }
            else if (outputFileExtension == ".glb" || outputFileExtension == ".gltf")
            {
                writer = std::make_unique<GltfWriter>();
                writeProcess = std::make_unique<MeshWriteProcess>(inInputFile, std::move(writer));
            }
            else if (outputFileExtension == ".fbx")
            {
                writer = std::make_unique<FBXWriter>();
                writeProcess = std::make_unique<MeshWriteProcess>(inInputFile, std::move(writer));
            }
            else if (outputFileExtension == ".pdf")
            {
                std::unique_ptr<PDFWriter> writer2 = std::make_unique<PDFWriter>();
                writer2->SetInputFile(inInputFile);
                writeProcess = std::make_unique<PDFWriteProcess>(inInputFile, std::move(writer2));
            }
            else
            {
                LOG_ERROR("Unsupported file extension: {}", outputFileExtension.c_str());
                return dtkNoError;
            }
            err = writeProcess->WriteDocument(tmpDoc, outputFile.c_str()); //see WriteDocument.cpp
            if( err != dtkNoError )
            {
                LOG_ERROR("Error WriteDocument: {}, {}",  (int)err, dtkTypeError( err ).c_str());
                return err;
            } 
        }
        else
        {
            LOG_ERROR("Error OpenDocument: {}, {}",  (int)err, dtkTypeError( err ).c_str());
            return err;
        }
    
        //We close the opened document
        err = dtkAPI->EndDocument( tmpDoc );

        return err;
    }

    void IOImpl::EnableReaders()    
    {
        // Supported Format : extensions
        //Acis :        sat
        //Catiav4 :     model (both 2d and 3d) session, dlv 
        //Catiav5 :     CATDrawing,CATPart,CATProduct
        //Cgr :         cgr
        //Dwg :         dwg
        //Iges :        igs,iges
        //Inventor :    ipt,iam
        //Jt :          jt
        //Parasolid :   x_t,x_b,xmt
        //Proe :        prt,asm
        //Solidedge :   par,asm
        //Solidworks :  sldprt,sldasm
        //Step :        step,stp
        //Unigraphics:  prt (both 2d and 3d)
        //Vda :          vda

        //First You have to Enable the readers you want to use 

        _3dmReader::Enable();
        AcisReader::Enable();
    #ifndef Linux
    #ifndef ARM64
        CaddsReader::Enable();
    #endif
    #endif
        CatiaV4Reader::Enable();
        CatiaV5Reader::Enable();
        CatiaV6Reader::Enable();
        CerconReader::Enable();
        CerecReader::Enable();
        CgmReader::Enable();
        CgrReader::Enable();
        CreoviewReader::Enable();
        DcmReader::Enable();
    #ifndef CENTOS
    #ifndef ARM64
        // DwgReader::Enable();
    #endif
    #endif
        F3dReader::Enable();
        FbxReader::Enable();
        GltfReader::Enable();
        IfcReader::Enable();
        IgesReader::Enable();
        InvReader::Enable();
        JtReader::Enable();
        LavaReader::Enable();
        NavisReader::Enable();
        ObjReader::Enable();
        PlmXmlReader::Enable();
        ProCeraReader::Enable();
        ProeReader::Enable();
    #ifdef USE_PSKERNEL
        XmtReader::Enable();
    #else
        PsReader::Enable();
    #endif
        QifReader::Enable();
        RevitReader::Enable();
        SmgReader::Enable();
        StlReader::Enable();
        StepReader::Enable();
    #ifndef ARM64
        SolidEdgeReader::Enable();
    #endif
        SwReader::Enable();
        UgReader::Enable();
        VdaReader::Enable();
    }



    IO::IO() : m_impl(std::make_unique<IOImpl>())
    {
    }

    IO &IO::GetInstance()
    {
        static IO instance;
        return instance;
    }

    int IO::Initialize()
    {
        InitSpdlog();

        // //////Setting Project Options//////
        // // Activates / deactivates 3D PDF file creation with geometrical, PMI, assembly, materials datas...
        // // This option requires Datakit tessellation
        // ActivatePdfDump(DTK_TRUE);

        // // Activates / deactivates dumping of DTK classes to XML file
        // ActivateXmlDump(DTK_TRUE);

        m_impl->EnableReaders();
        std::locale::global(std::locale("en_US.UTF-8"));	

        //Dtk_API need a working directory where it can write
#ifndef Linux
        wchar_t temporaryWorkingDirectory[1024];
        wchar_t *retwc;
        retwc = _wgetcwd(temporaryWorkingDirectory, 1024);
#else
        char temporaryWorkingDirectory[1024];
        getcwd(temporaryWorkingDirectory, 1024);
#endif

        //Then You have to Start DATAKIT API with a temporary directory
        Dtk_ErrorStatus errorStatus = dtkNoError;
        LOG_INFO("Starting DATAKIT API ");
        Dtk_API * dtkAPI = Dtk_API::StartAPI(temporaryWorkingDirectory, errorStatus, "");
        if (dtkAPI == NULL)
        {
            LOG_ERROR("Can't Start DATAKIT API : {}",  (int)errorStatus);
            return dtkErrorAPINotStarted; 
        }
        return errorStatus;
    }

    int IO::Terminate()
    {
        auto dtkAPI = Dtk_API::GetAPI();
        if(dtkAPI)
        {
            Dtk_API::StopAPI(dtkAPI);
        }
        return dtkNoError;
    }


    int IO::Convert(const std::string& srcFilePath, const std::string& destFilePath)
    {
        auto extension = StringUtil::ToLowerCase(FileUtil::GetExtension(srcFilePath));
        //if(extension != ".stp" && extension != ".step")
        // {
        //     return dtkErrorTypeNotsupported;
        // }

		Dtk_string inputFile = srcFilePath.c_str();
        Dtk_string outputFile = destFilePath.c_str();
		time_t startTime;
		time(&(startTime));
		auto curError = (Dtk_ErrorStatus)m_impl->ProcessCADFile(srcFilePath, destFilePath);

		time_t stopTime;
		time(&(stopTime));

		if (curError)
		{
			LOG_ERROR("error processing file {} with error {}", inputFile.c_str(), dtkTypeError(curError).c_str());
		}
		else
		{
			double elaps_time = difftime(stopTime, startTime);
            LOG_INFO("file {} is processed in {} seconds", inputFile.c_str(), elaps_time);
		}
        return curError;
    }
}

